# -*- encoding: utf8 -*-
"A wrapper module to get EXIF and IPTC tags from a picture"
#
# Copyright 2007-2008 Håvard Gulldahl
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

try:
  import PIL
  PIL_AVAILABLE = True
except ImportError:
  PIL_AVAILABLE = False

try:
  import iptcdata
  IPTCDATA_AVAILABLE = True
except ImportError:
  IPTCDATA_AVAILABLE = False

try:
  import pyexiv2
  PYEXIV_AVAILABLE = True
except ImportError:
  PYEXIV_AVAILABLE = False

class Tagreader:
  _tags = {'keywords':[], 'caption': '', 'location': None}
  def __init__(self, filename):
    if isinstance(filename, unicode): filename = filename.encode('utf8')
    if PYEXIV_AVAILABLE:
      self.pyexiv_extract(filename)
    elif IPTCDATA_AVAILABLE:
      self.iptcdata_extract(filename)
    elif PIL_AVAILABLE:
      self.pil_extract(filename)

  def pyexiv_extract(self, filename):
    i = pyexiv2.Image(filename)
    i.readMetadata()
    #print i
    latkey = 'Exif.GPSInfo.GPSLatitude'
    lonkey = 'Exif.GPSInfo.GPSLongitude'
    try:
      if type(i['Iptc.Application2.Keywords']) in (str, unicode):
        self._tags['keywords'] = [ i['Iptc.Application2.Keywords'] ,]
      else:
        self._tags['keywords'] = list(i['Iptc.Application2.Keywords'])
      self._tags['caption'] = i['Iptc.Application2.Caption']
      # some pyexiv versions have a gps bug, so first we do it stoopid...
      lat = float(i[latkey][0])
      lon = float(i[lonkey][0])
      if i['Exif.GPSInfo.GPSLatitudeRef'] == 'S':
        lat *= -1
      if i['Exif.GPSInfo.GPSLongitudeRef'] == 'W':
        lon *= -1
      self._tags['location'] = ( lat, lon )
      # ... then we try to do it properly (the except clause will fire in older versions)
      lat = self.gps_tuple_to_coords(i[latkey])
      if i['Exif.GPSInfo.GPSLatitudeRef'] == 'S':
        lat *= -1
      lon = self.gps_tuple_to_coords(i[lonkey])
      if i['Exif.GPSInfo.GPSLongitudeRef'] == 'W':
        lon *= -1
      self._tags['location'] = ( lat, lon )
    except (KeyError, IndexError,TypeError):
      pass

  def iptcdata_extract(self, filename):
    i = iptcdata.open(filename)
    self._tags['keywords'] = [ x.value for x in i.datasets if x.title == 'Keywords' ]
    self._tags['caption'] = " ".join( [ x.value for x in i.datasets if x.title == 'Caption/Abstract' ] )
    #self._tags['location'] = ()
    i.close()

  def pil_extract(self, filename):
    from PIL import Image, IptcImagePlugin
    try:
      iptc = IptcImagePlugin.IptcImageFile(filename)
    except SyntaxError: # PIL doesn't like this IPTC
      pass

    from PIL.ExifTags import TAGS
    i = Image.open(filename)
    info = i._getexif()
    ret = {}
    for tag,value in info.items():
      decoded = TAGS.get(tag, tag)
      if type(value) in (str, unicode):
        ret[decoded] = self.string_decode(value)
      else:
        ret[decoded] = value
    from pprint import pprint as pp
    #pp(ret)
    if ret.has_key('UserComment'):
      self._tags['caption'] = ret['UserComment']
    try:
      lat = self.gps_tuple_to_coords(ret['GPSInfo'][2])
      lon = self.gps_tuple_to_coords(ret['GPSInfo'][4])

      if ret['GPSInfo'][1] == 'S':
        lat *= -1

      if ret['GPSInfo'][3] == 'W':
        lon *= -1

      self._tags['location'] = ( lat, lon )
    except (KeyError,):
      pass

  def iptcinfo_extract(self, filename):
    from iptcinfo import IPTCInfo
    info = IPTCInfo(filename)
    if len(info.data) < 4: return # no iptc info found
    self._tags['keywords'] = info.keywords

  def string_decode(self, s):
    """Decode a exif/iptc string according to embedded charset info, also stripping nul bytes"""
    ## from exiv2 man page:
    #The format of Exif Comment values includes an optional charset specification at the beginning:
    #[charset=Ascii|Jis|Unicode|Undefined ]comment
    s = s.replace('\x00', '')
    if s.lower().startswith('jis'): s = s.decode('jis')
    if s.lower().startswith('unicode'): s = s.decode('utf8')
    for enc in ('jis', 'unicode', 'ascii'):
      if s.lower().startswith(enc):
        s = s.replace(enc, '').replace(enc.upper(), '') ## UGGGH..
    return s

  def gps_tuple_to_coords(self, tup):
    return tup[0][0] + reduce(lambda x, y: x+float(y[0])/float(y[1]*60.0), tup[1:], (0) )

  def getKeywords(self):
    return self._tags['keywords']

  def getCaption(self):
    return self._tags['caption']

  def getLocation(self):
    return self._tags['location']

if __name__ == '__main__':
  import sys
  t = Tagreader(sys.argv[1])
  print "PYEXIV2:", PYEXIV_AVAILABLE
  if PYEXIV_AVAILABLE:
    t.pyexiv_extract(sys.argv[1])
    print "keywords:", t.getKeywords()
    print "comment:", t.getCaption()
    print "location:", t.getLocation()
  print "IPTCDATA:", IPTCDATA_AVAILABLE
  if IPTCDATA_AVAILABLE:
    t.iptcdata_extract(sys.argv[1])
    print "keywords:", t.getKeywords()
    print "comment:", t.getCaption()
    print "location:", t.getLocation()
  print "PIL:", PIL_AVAILABLE
  if PIL_AVAILABLE:
    t.pil_extract(sys.argv[1])
    print "keywords:", t.getKeywords()
    print "comment:", t.getCaption()
    print "location:", t.getLocation()
